﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Interop;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace Gw2Overlay
{
    public class OverlayWindow : Window, IDisposable
    {
        private static int nextId = 0;
        private int _hkId;
        private HwndSource _source;
        private bool _clickable;
        private NativeMethods.HookProc _msCallback = null;
        private IntPtr _msHook;

        private bool _master = false;

        public OverlayWindow(bool master = false)
        {
            _master = master;
            SourceInitialized += Window_SourceInitialized;
            Closed += Window_Closed;
        }

        ~OverlayWindow()
        {
            Dispose();
        }

        public void Dispose()
        {
            if (null != _source)
            {
                _source.RemoveHook(MessageHook);
                _source = null;
            }
            if (_master)
            {
                NativeMethods.UnhookWindowsHookEx(_msHook);
            }
        }

        protected void Window_SourceInitialized(object sender, EventArgs e)
        {
            IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
            _source = HwndSource.FromHwnd(hwnd);
            _source.AddHook(MessageHook);
            
            MakeClickThrough(false);
            MouseLeftButtonDown += delegate { DragMove(); };

            if (_master)
            {
                RegisterHotkey();
                _msCallback = new NativeMethods.HookProc(MouseHookCallback);
                using (Process curProcess = Process.GetCurrentProcess())
                using (ProcessModule curModule = curProcess.MainModule)
                {
                    _msHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, _msCallback, NativeMethods.GetModuleHandle(curModule.ModuleName), 0);
                }
            }
        }

        protected void Window_Closed(object sender, EventArgs e)
        {
            UnregisterHotkey();
            Dispose();
        }

        private void RegisterHotkey()
        {
            IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
            _hkId = nextId++;
            NativeMethods.RegisterHotKey(hwnd, NativeMethods.VK_F8, 0, NativeMethods.VK_F8);
        }

        protected void UnregisterHotkey()
        {
            IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
            NativeMethods.UnregisterHotKey(hwnd, NativeMethods.VK_F8);
        }

        virtual protected void OnInteractable(bool interactable)
        {
        }

        virtual protected void MakeClickThroughAfter(bool transparent)
        {
        }

        public void MakeClickThrough(bool transparent)
        {
            IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
            int extendedStyle = NativeMethods.GetWindowLong(hwnd, NativeMethods.GWL_EXSTYLE);
            if (transparent)
            {
                extendedStyle |= NativeMethods.WS_EX_TRANSPARENT;
                _clickable = false;
                OnInteractable(false);
            }
            else
            {
                extendedStyle &= ~NativeMethods.WS_EX_TRANSPARENT;
                _clickable = true;
                OnInteractable(true);
            }
            NativeMethods.SetWindowLong(hwnd, NativeMethods.GWL_EXSTYLE, extendedStyle);
        }

        private IntPtr MouseHookCallback(int code, IntPtr wParam, IntPtr lParam)
        {
            if (code < 0)
            {
                return NativeMethods.CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
            }
            NativeMethods.MSLLHOOKSTRUCT msinfo = (NativeMethods.MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(NativeMethods.MSLLHOOKSTRUCT));
            DoHitTest(new Point(msinfo.pt.X, msinfo.pt.Y));
            return NativeMethods.CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
        }

        virtual public void DoHitTest(Point pt)
        {
            Debug.WriteLine(pt.X + "," + pt.Y);
            return;
        }

        private IntPtr MessageHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            switch (msg)
            {
                case NativeMethods.WM_HOTKEY:                    
                    MakeClickThrough(_clickable);
                    MakeClickThroughAfter(_clickable);
                    handled = true;
                    break;
            }
            return IntPtr.Zero;
        }
    }

    public class NativeMethods
    {

        public const int WM_HOTKEY = 0x0312;
        public const int VK_F8 = 0x77;
        public const int GWL_EXSTYLE = -20;
        public const int WS_EX_TRANSPARENT = 0x20;
        public const int WH_MOUSE_LL = 14;

        [DllImport("user32.dll")]
        public static extern int GetWindowLong(IntPtr hwnd, int index);
        [DllImport("user32.dll")]
        public static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool RegisterHotKey(IntPtr hWnd, int id, int modifiers, int vk);
        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        public delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
        [StructLayout(LayoutKind.Sequential)]
        public struct MSLLHOOKSTRUCT
        {
            public POINT pt;
            public int mouseData;
            public int flags;
            public int time;
            public UIntPtr dwExtraInfo;
        }
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int X;
            public int Y;
        }
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr SetWindowsHookEx(int hookType, HookProc lpfn, IntPtr hMod, int dwThreadId);
        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool UnhookWindowsHookEx(IntPtr hhk);
        [DllImport("user32.dll")]
        public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);
    }
}
